/**************************************************************************************************
* Title: SumRenderManager.cpp
* Author: Gael Huber
* Description: Handles entirety of rendering subsystem.
**************************************************************************************************/
#include "SumRenderManager.h"

XMMATRIX view;
XMMATRIX& GetView(void) { return view; }

/**************************************************************************************************
* Initialize the singleton instance of this class to 0
**************************************************************************************************/
template <> RenderManager* Singleton<RenderManager>::singleton = 0;

/**************************************************************************************************
* Constructor
**************************************************************************************************/
RenderManager::RenderManager(void)
{ }

/**************************************************************************************************
* Destructor
**************************************************************************************************/
RenderManager::~RenderManager(void)
{ }

/**************************************************************************************************
* Initialize Direct3D
**************************************************************************************************/
void RenderManager::startUp(int nCmdShow) {
	// Create the window
	window = new RenderWindow(800, 600, nCmdShow);

	// Initialize Direct3D
	initDirect3D();

	// Initialize the rendering pipeling
	initPipeline();	
}

/**************************************************************************************************
* Shut down Direct3D
**************************************************************************************************/
void RenderManager::shutDown(void) {
	// Close and release all existing COM objects
	if(deviceContext) deviceContext->ClearState();

	if(constantBuffer) constantBuffer->Release();
	if(vertexLayout) vertexLayout->Release();
	if(vertexShader) vertexShader->Release();
	if(pixelShader) pixelShader->Release();
	if(backBuffer) backBuffer->Release();
	if(swapChain) swapChain->Release();
	if(deviceContext) deviceContext->Release();
	if(device) device->Release();

	// Close the window after everything has been released
	if(window)	delete window;
}

/**************************************************************************************************
* Initialize Direct3D 11
**************************************************************************************************/
void RenderManager::initDirect3D(void) {
	// Result used for initializing
	HRESULT hr = S_OK;

	// Get the client rectangle
	RECT wr;
	GetClientRect(GetHWND(), &wr);
	UINT width = wr.right - wr.left;
	UINT height = wr.bottom - wr.top;

	// Device flags
	UINT createDeviceFlags = 0;

	// Driver information
	D3D_DRIVER_TYPE driverTypes[] = 
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE
	};
	UINT numDriverTypes = ARRAYSIZE(driverTypes);

	// Feature levels
	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0
	};
	UINT numFeatureLevels = ARRAYSIZE(featureLevels);
	featureLevel = D3D_FEATURE_LEVEL_11_0;	// Default feature level
		
	// Create a struct to hold information about the swap chain
	DXGI_SWAP_CHAIN_DESC scd;

	// Clear out the memory for the struct to use
	ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

	// Fill the swap chain description with needed information
	scd.BufferCount = 1;	// One back buffer
	scd.BufferDesc.Width = width;
	scd.BufferDesc.Height = height;
	scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;	// Use 32-bit color
	scd.BufferDesc.RefreshRate.Numerator = 60;
	scd.BufferDesc.RefreshRate.Denominator = 1;
	scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;	// How swap chain is to be used
	scd.OutputWindow = GetHWND();	// The window into which the program will render
	scd.SampleDesc.Count = 1;	// How many multisamples
	scd.SampleDesc.Quality = 0;	// Quality of samples
	scd.Windowed = TRUE;	// Windowed vs. fullscreen

	// Initialize driver information
	for(UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; ++driverTypeIndex) {
		driverType = driverTypes[driverTypeIndex];
		
		// Create a device, device context, and swap chain using the information in scd
		hr = D3D11CreateDeviceAndSwapChain(
			NULL, 
			driverType, 
			NULL, 
			createDeviceFlags, 
			featureLevels, 
			numFeatureLevels, 
			D3D11_SDK_VERSION, 
			&scd, 
			&swapChain, 
			&device, 
			&featureLevel, 
			&deviceContext);

		// If the device was successfully created, we may return with our current feature level
		if(SUCCEEDED(hr)) {
			break;
		}
	}

	// Get the address of the back buffer
	ID3D11Texture2D* pBackBuffer;
	swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

	// Use the back buffer address to create the render target
	device->CreateRenderTargetView(pBackBuffer, NULL, &backBuffer);
	pBackBuffer->Release();

	// Create the depth stencil texture
	D3D11_TEXTURE2D_DESC descDepth;
	ZeroMemory(&descDepth, sizeof(descDepth));
	descDepth.Width = width;
	descDepth.Height = height;
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	descDepth.Usage = D3D11_USAGE_DEFAULT;
	descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MipLevels = 0;
	device->CreateTexture2D(&descDepth, NULL, &depthStencil);

	// Create depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory(&descDSV, sizeof(descDSV));
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	device->CreateDepthStencilView(depthStencil, &descDSV, &depthStencilView);

	// Set the render target as the back buffer
	deviceContext->OMSetRenderTargets(1, &backBuffer, depthStencilView);

	// Set the viewport
	D3D11_VIEWPORT viewport;
	ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
	viewport.TopLeftX = 0;
	viewport.TopLeftY = 0;
	viewport.Width = (float) window->getWidth();
	viewport.Height = (float) window->getHeight();
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	deviceContext->RSSetViewports(1, &viewport);

	// Set backbuffer clear color
	clearColor = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
}

/**************************************************************************************************
* Initialize the rendering pipeline
**************************************************************************************************/
void RenderManager::initPipeline(void) {
	// Used for checking initialization success
	HRESULT hr = S_OK;
	
	// Compile the vertex shader
	ID3DBlob* vsBlob = NULL;
	hr = compileShaderFromFile(L"..\\Sum Engine\\Shaders\\BasicShader2.fx", "VS", "vs_4_0", &vsBlob);
	
	// TODO: Check failure

	// Create the vertex shader
	hr = device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), NULL, &vertexShader);

	// TODO: Check failure

	// Define the input layout
	D3D11_INPUT_ELEMENT_DESC layout[] = 
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
	};
	UINT numElements = ARRAYSIZE(layout);

	// Create the input layout
	hr = device->CreateInputLayout(layout, numElements, vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &vertexLayout);
	vsBlob->Release();

	// Set input layout
	deviceContext->IASetInputLayout(vertexLayout);

	// Compile the pixel shader
	ID3DBlob* psBlob = NULL;
	hr = compileShaderFromFile(L"..\\Sum Engine\\Shaders\\BasicShader2.fx", "PS", "ps_4_0", &psBlob);

	// TODO: Check failure

	// Create the pixel shader
	hr = device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), NULL, &pixelShader);
	psBlob->Release();

	// Set primitive topology
	deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Create the constant buffer
	D3D11_BUFFER_DESC bd;
	ZeroMemory(&bd, sizeof(D3D11_BUFFER_DESC));
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(WVPMatrix);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	hr = device->CreateBuffer(&bd, NULL, &constantBuffer);

	// TODO: Check failure
	world = XMMatrixIdentity();

	// Initialize view matrix
	XMVECTOR eye = XMVectorSet(0.0f, 1.0f, -5.0f, 0.0f);
	XMVECTOR at = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);
	view = XMMatrixLookAtLH(eye, at, up);

	// Initialize projection matrix
	RECT rc;
    GetClientRect(GetHWND(), &rc);
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;
	projection = XMMatrixPerspectiveFovLH(XM_PIDIV2, width / (FLOAT) height, 0.01f, 100.0f);
}

/**************************************************************************************************
* Compile a shader given a filename
**************************************************************************************************/
HRESULT RenderManager::compileShaderFromFile(WCHAR* filename, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** blobOut) {
	// Used for testing initialization failures
	HRESULT hr = S_OK;

	// Shader flags
	DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;

	// Blob used for catching any errors
	ID3DBlob* errorBlob;
	
	// Compile the shader from file
	hr = D3DX11CompileFromFile(
		filename, 
		NULL, 
		NULL, 
		entryPoint, 
		shaderModel, 
		dwShaderFlags, 
		0, 
		NULL, 
		blobOut, 
		&errorBlob, 
		NULL);

	// Check for failure
	if(FAILED(hr)) {
		if(errorBlob != NULL) {
			OutputDebugStringA((char*)errorBlob->GetBufferPointer());
		}
		if(errorBlob) errorBlob->Release();
		return hr;
	}

	// If anything entered the error blob but otherwise compilation was successful, release the blob
	if(errorBlob) errorBlob->Release();

	return S_OK;
}

/**************************************************************************************************
* Create a data buffer
**************************************************************************************************/
HRESULT RenderManager::createBuffer(D3D11_BUFFER_DESC* bd, D3D11_SUBRESOURCE_DATA* initData, ID3D11Buffer** buffer) {
	return device->CreateBuffer(bd, initData, buffer);
}

/**************************************************************************************************
* Render the current scene
**************************************************************************************************/
void RenderManager::renderScene(void) {
	// Use a critical section to ensure rendering is only happening once at a time
	//static CriticalSection cs;

	// Enter the critical section
	//cs.enter();

	static Timer timer;
	static int numFrames = 0;
	++numFrames;
	static float tt = 0.0f;
	tt += timer.deltaTime_f_no_max();
	if(tt > 1.0f) {
		char str[20]  = "";
		_itoa_s(numFrames, str, 10);
		OutputDebugStringA(str);
		OutputDebugString(L"\r\n");
		numFrames = 0;
		tt = 0.0f;
	}

	// Update our time
	static float t = 0.0f;
	if(driverType == D3D_DRIVER_TYPE_REFERENCE) {
		t += (float) XM_PI * 0.0125f;
	} else {
		static DWORD dwTimeStart = 0;
		DWORD dwTimeCur = GetTickCount();
		if(dwTimeStart == 0) {
			dwTimeStart = dwTimeCur;
		}
		t = (dwTimeCur - dwTimeStart) / 1000.0f;
	}

	// Animate the cube
	world = XMMatrixRotationY(t);

	XMMATRIX world2 = XMMatrixMultiply(world, XMMatrixTranslation(3.0f, 0.0f, 0.0f));

	// Clear the back buffer to black
	deviceContext->ClearRenderTargetView(backBuffer, clearColor);

	// Clear the depth buffer to 1.0f (max depth)
	deviceContext->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);

	// TODO:	
	// RENDER
	// Update constants (camera, etc.)
	// Render scenery
	// Render active objects
	// Render particles
	// Render text

	// Update the camera
	WVPMatrix cb;
	cb.world = XMMatrixTranspose(world);
	cb.viewProj = XMMatrixTranspose(XMMatrixMultiply(view, projection));
	deviceContext->UpdateSubresource(constantBuffer, 0, NULL, &cb, 0, 0);

	UINT stride = sizeof(SimpleVertex);
	UINT offset = 0;
	deviceContext->IASetVertexBuffers(0, 1, &mesh.vertexBuffer, &stride, &offset);

	// Set index buffer
	deviceContext->IASetIndexBuffer(mesh.indexBuffer, DXGI_FORMAT_R16_UINT, 0);

	// Update camera variables
	//WVPMatrix cb;
	//cb.world = XMMatrixTranspose(world);
	//cb.view = XMMatrixTranspose(view);
	//cb.projection = XMMatrixTranspose(projection);
	//deviceContext->UpdateSubresource(constantBuffer, 0, NULL, &cb, 0, 0);

	// Render cube
	deviceContext->VSSetShader(vertexShader, NULL, 0);
	deviceContext->VSSetConstantBuffers(0, 1, &constantBuffer);
	deviceContext->PSSetShader(pixelShader, NULL, 0);
	deviceContext->DrawIndexed(36, 0, 0);

	cb.world = XMMatrixTranspose(world2);
	deviceContext->UpdateSubresource(constantBuffer, 0, NULL, &cb, 0, 0);
	deviceContext->DrawIndexed(36, 0, 0);

	// Switch the back buffer and the front buffer
	swapChain->Present(0, 0);

	// Leave the critical section
	//cs.leave();
}

/**************************************************************************************************
* Calls the actual scene render
**************************************************************************************************/
void RenderManager::kickRender(void* p) {
	static_cast<RenderManager*>(p)->renderScene();
}